package edu.ntu.cloudroid.server.backendservice;

import java.util.ArrayList;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.google.appengine.api.datastore.Key;

import edu.ntu.cloudroid.model.IDs;
import edu.ntu.cloudroid.model.PMF;
import edu.ntu.cloudroid.model.data.AbstractData;
import edu.ntu.cloudroid.model.friend.GroupData;
import edu.ntu.cloudroid.model.policy.AbstractPrivacyPreference;
import edu.ntu.cloudroid.model.profile.ProfileData;
import edu.ntu.cloudroid.server.RequestServlet;
import edu.ntu.cloudroid.server.UploadServlet;
import edu.ntu.cloudroid.server.backendservice.util.CloudDroidCache;


/**
 * @author ttadinh Similar to DataService, this provide methods for storing
 *         generic AbstractPrivacyPreference objects
 */
public class PolicyService {
	private static CloudDroidCache<Key, AbstractPrivacyPreference> cache = new CloudDroidCache<Key, AbstractPrivacyPreference>(CloudDroidCache.SIZE);
	
	public static void purgeCache(){
		 cache = new CloudDroidCache<Key, AbstractPrivacyPreference>(CloudDroidCache.SIZE);
	}
	
	//The profile must already exists, it is updated here. 
	public static ProfileData updateDefaultPolicy(ProfileData person,
			AbstractPrivacyPreference pref, int prefId){
		
		//first, delete the old policy for the same person
		//deleteDefaultPolicy(person);
		
		//then update the new one
		PersistenceManager pm = PMF.get().getPersistenceManager();	
		AbstractPrivacyPreference app = null;
		try {		
			pm.currentTransaction().begin();			
			app = pm.detachCopy(pm.makePersistent(pref));
			pm.currentTransaction().commit();
		}
		finally{
			pm.close();
		}
		
		pm = PMF.get().getPersistenceManager();
		ProfileData newProf = null;
		try{
			pm.currentTransaction().begin();			
			person.setDefaultPrivacyPreference(app.getKey());			
			person.setPrivacyPreferenceId(prefId);	
			pm.makePersistent(person);
			//newProf = pm.detachCopy(pm.makePersistent(person));			
			pm.currentTransaction().commit();		
		} finally {
			if (pm.currentTransaction().isActive()) {
				pm.currentTransaction().rollback();
			}
			pm.close();
		}
		
		cache.put(person.getKey(), pref);
		
		return newProf;
	}
	
	//delete the current policy of the profile
	public static void deleteDefaultPolicy(ProfileData p){
		cache.delete(p.getKey());
		
		if (p.getDefaultPrivacyPreference() == null)
			return;
		try{
			AbstractPrivacyPreference app = getDefaultPolicy(p);
			PersistenceManager pm = PMF.get().getPersistenceManager();
			pm.deletePersistent(app);
			pm.close();
		}
		catch (Exception e){
			System.err.println("Error deleting default policy for "+p.getEmailAddress());
		}
	}
	
	public static void deleteAllPolicies(int type) throws Exception{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		/*pm.getFetchPlan().addGroup(AbstractData.FETCH_GROUP_NAME);
		pm.getFetchPlan().setMaxFetchDepth(2);*/	
		List<AbstractData> returnList = new ArrayList<AbstractData>();
		long start = System.currentTimeMillis();
		try {
			Query query = pm.newQuery(getPolicyClass(type));
			
			//query.setFilter("profileKey == personParam && timestamp > timestampParam");
			
			//query.declareParameters(Key.class.getName() + " personParam, " + Date.class.getName() + " timestampParam");
			//query.setOrdering("timestamp asc");
			
			@SuppressWarnings("unchecked")
			List<AbstractPrivacyPreference> result = (List<AbstractPrivacyPreference>) query.execute();
			
			for (AbstractPrivacyPreference v : result){
				pm.deletePersistent(v);
			}			
			//query.execute();
			
			long end = System.currentTimeMillis();
			UploadServlet.logger.info("policy delete time = "+(end-start));
		} finally {
			pm.close();		
		}
	}
	
	public static void deleteAllPoliciesForUser(int type, String userName) throws Exception{
		//ProfileData profile = ProfileService.getProfile(userName);
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		/*pm.getFetchPlan().addGroup(AbstractData.FETCH_GROUP_NAME);
		pm.getFetchPlan().setMaxFetchDepth(2);*/	
		List<AbstractData> returnList = new ArrayList<AbstractData>();
		long start = System.currentTimeMillis();
		try {
			Query query = pm.newQuery(getPolicyClass(type), "userName == person");
			query.declareParameters("String person");
			//query.setFilter("profileKey == personParam && timestamp > timestampParam");
			
			//query.declareParameters(Key.class.getName() + " personParam, " + Date.class.getName() + " timestampParam");
			//query.setOrdering("timestamp asc");
			
			@SuppressWarnings("unchecked")
			List<AbstractPrivacyPreference> result = (List<AbstractPrivacyPreference>) query
					.execute(userName);
			
			for (AbstractPrivacyPreference v : result){
				pm.deletePersistent(v);
			}			
			//query.execute();
			
			long end = System.currentTimeMillis();
			UploadServlet.logger.info("policy delete time = "+(end-start));
		} finally {
			pm.close();		
		}
	}
	
	//delete the current policy of the profile
	public static void deleteGroupPolicy(GroupData group){
		if (group.getGroupPolicyKey() == null)
			return;
		try {
			cache.delete(group.getKey());
			AbstractPrivacyPreference app = getGroupPolicy(group);
			PersistenceManager pm = PMF.get().getPersistenceManager();
			pm.deletePersistent(app);
			pm.close();
		}
			catch (Exception e){
				System.err.println("Error deleting group policy for "+group.getGroupName());
			}
		}
		
	//first, find the group with specific name, then update the profile
	public static void updateGroupPolicy(ProfileData profile,
			AbstractPrivacyPreference pref, String groupName, int privacyType) {
		GroupData group = GroupService.findGroupWithName(groupName);		
		// delete it first		
		deleteGroupPolicy(group);

		// then update the new one
		PersistenceManager pm = PMF.get().getPersistenceManager();
		AbstractPrivacyPreference app = null;
		try {
			pm.currentTransaction().begin();
			app = pm.detachCopy(pm.makePersistent(pref));
			pm.currentTransaction().commit();
		} finally {
			pm.close();
		}

		pm = PMF.get().getPersistenceManager();
		try {
			pm.currentTransaction().begin();
			group.setGroupPolicy(app.getKey());
			group.setPrivacyPrefId(privacyType);
			pm.makePersistent(group);
			pm.currentTransaction().commit();
			cache.put(group.getKey(), app);
		} finally {
			if (pm.currentTransaction().isActive()) {
				pm.currentTransaction().rollback();
			}
			pm.close();
		}
	}

	
	public static AbstractPrivacyPreference getDefaultPolicy(ProfileData person) throws Exception{
		AbstractPrivacyPreference app = cache.get(person.getKey());
		if (null == app) {
			PersistenceManager pm = PMF.get().getPersistenceManager();
			try {
				pm.currentTransaction().begin();
				Class<?> prefType = getPolicyClass(person
						.getPrivacyPreferenceId());
				app = (AbstractPrivacyPreference) pm.detachCopy(pm
						.getObjectById(prefType,
								person.getDefaultPrivacyPreference()));

				cache.put(person.getKey(), app);
				pm.currentTransaction().commit();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (pm.currentTransaction().isActive())
					pm.currentTransaction().rollback();
				pm.close();
			}
		}
		return app;
	}
	
	public static AbstractPrivacyPreference getGroupPolicy(GroupData group) throws Exception{
		AbstractPrivacyPreference app = cache.get(group.getKey());
		if (null == app) {
		PersistenceManager pm = PMF.get().getPersistenceManager();	
		
		try {
			pm.currentTransaction().begin();			
			Class prefType = getPolicyClass(group.getPrivacyPrefId());
			app = (AbstractPrivacyPreference) pm
					.detachCopy(pm.getObjectById(prefType, group.getGroupPolicyKey()));			
			pm.currentTransaction().commit();						
		} 
		catch (Exception e){			
			e.printStackTrace();
		}
		finally {
			if (pm.currentTransaction().isActive())
				pm.currentTransaction().rollback();
			pm.close();
		}
		}
		return app;
	}
	
	public static Object getPolicyObject(int id) throws Exception{
		Class cl = Class.forName("edu.ntu.cloudroid.model.policy."+IDs.PolicyID.values()[id].name());		
		return cl.newInstance();
	}
	
	public static Object getPolicyBuilderObject(int id) throws Exception {
		Class cl = Class.forName("edu.ntu.cloudroid.xacml.builder."
				+ IDs.PolicyID.values()[id].name());
		return cl.newInstance();
	} 
	
	public static Class getPolicyClass(int id) throws Exception {
		//System.out.println("PolicyID " + id + " " + IDs.PolicyID.values()[id].name());
		return Class.forName("edu.ntu.cloudroid.model.policy."
				+ IDs.PolicyID.values()[id].name());
	}
	
	
}
